<!--
vue3-ts 基于 tianai-captcha 验证组件
author：stary1993@https://gitee.com/stary1993
依赖：axios、animate.css
服务端生成：https://gitee.com/stary1993/kite-captcha-spring-boot-starter

-->
<template>
  <div v-if="globalData.show" id="kite-captcha-parent">
    <div id="kite-captcha-bg-img" :style="{backgroundImage: 'url('+globalData.data.boxBackgroundImage+')'}"></div>
    <div id="kite-captcha-box">
      <img v-show="globalData.loading && globalData.data.loadingImage" id="kite-captcha-loading"
           :src="globalData.data.loadingImage"
           alt="loading">
      <div v-if="globalData.type === 'SLIDER'" id="kite-captcha" :class="globalData.captchaClass"
           class="kite-captcha-slider animate__animated">
        <div class="kite-slider-tips">
          <span>拖动滑块完成拼图</span>
        </div>
        <div class="kite-captcha-content">
          <div id="kite-captcha-bg-img-box" class="kite-captcha-bg-img-box">
            <img id="kite-slider-bg-img" :src="globalData.data.backgroundImage" alt=""/>
            <canvas id="kite-captcha-slider-bg-canvas"></canvas>
          </div>
          <div id="kite-slider-move-img-box" :style="globalData.sliderImgStyles" class="kite-slider-move-img-box">
            <img id="kite-captcha-slider-move-img" :src="globalData.data.templateImage" alt="">
          </div>
          <div id="kite-captcha-tips" :class="globalData.tipsClass" class="kite-captcha-tips">{{
              globalData.tipsContent
            }}
          </div>
        </div>
        <div class="kite-slider-move">
          <div class="kite-slider-move-track">
            <div id="kite-captcha-slider-move-track-mask"
                 :style="globalData.moveTracMaskStyles"
                 style="border-color: rgb(2, 152, 248); background-color: rgb(137, 210, 255);"></div>
            <div class="kite-slider-move-shadow"></div>
          </div>
          <div id="kite-captcha-slider-move-btn"
               :style="globalData.moveBtnStyles"
               class="kite-slider-move-btn"
               @mousedown="sliderDown($event)"
               @touchstart="sliderDown($event)">
          </div>
        </div>
      </div>
      <div v-else-if="globalData.type === 'ROTATE'" id="kite-captcha" :class="globalData.captchaClass"
           class="kite-captcha-slider kite-captcha-rotate animate__animated">
        <div class="kite-slider-tips">
          <span>拖动滑块完成拼图</span>
        </div>
        <div class="kite-captcha-content">
          <div id="kite-captcha-bg-img-box" class="kite-captcha-bg-img-box">
            <img id="kite-slider-bg-img" :src="globalData.data.backgroundImage" alt=""/>
            <canvas id="kite-captcha-slider-bg-canvas"></canvas>
          </div>
          <div id="kite-slider-move-img-box" class="kite-slider-move-img-box">
            <img id="kite-captcha-slider-move-img" :src="globalData.data.templateImage"
                 :style="globalData.rotateImgStyles" alt="">
          </div>
          <div id="kite-captcha-tips" :class="globalData.tipsClass" class="kite-captcha-tips">{{
              globalData.tipsContent
            }}
          </div>
        </div>
        <div class="kite-slider-move">
          <div class="kite-slider-move-track">
            <div id="kite-captcha-slider-move-track-mask"
                 :style="globalData.moveTracMaskStyles"
                 style="border-color: rgb(2, 152, 248); background-color: rgb(137, 210, 255);"></div>
            <div class="kite-slider-move-shadow"></div>
          </div>
          <div id="kite-captcha-slider-move-btn"
               :style="globalData.moveBtnStyles"
               class="kite-slider-move-btn"
               style="background-image: url('');"
               @mousedown="sliderDown($event)"
               @touchstart="sliderDown($event)">
          </div>
        </div>
      </div>
      <div v-else-if="globalData.type === 'CONCAT'" id="kite-captcha" :class="globalData.captchaClass"
           class="kite-captcha-slider kite-captcha-concat animate__animated">
        <div class="kite-slider-tips">
          <span>拖动滑块完成拼图</span>
        </div>
        <div class="kite-captcha-content">
          <div id="kite-captcha-slider-concat-img" :style="globalData.concatImgStyles"
               class="kite-captcha-slider-concat-img">
            <img id="kite-captcha-slider-concat-slider-img" alt=""/>
          </div>
          <div id="kite-captcha-slider-concat-bg-img" :style="globalData.concatBgImgStyles"
               class="kite-captcha-slider-concat-bg-img"></div>
          <div id="kite-captcha-tips" :class="globalData.tipsClass" class="kite-captcha-tips">{{
              globalData.tipsContent
            }}
          </div>
        </div>
        <div class="kite-slider-move">
          <div class="kite-slider-move-track">
            <div id="kite-captcha-slider-move-track-mask"
                 :style="globalData.moveTracMaskStyles"
                 style="border-color: rgb(2, 152, 248); background-color: rgb(137, 210, 255);"></div>
            <div class="kite-slider-move-shadow"></div>
          </div>
          <div id="kite-captcha-slider-move-btn"
               :style="globalData.moveBtnStyles"
               class="kite-slider-move-btn"
               style="background-image: url('');"
               @mousedown="sliderDown($event)"
               @touchstart="sliderDown($event)">
          </div>
        </div>
      </div>
      <div v-else-if="globalData.type === 'WORD_IMAGE_CLICK'" id="kite-captcha" :class="globalData.captchaClass"
           class="kite-captcha-slider kite-captcha-work-click animate__animated">
        <div class="kite-work-click-tips">
          <span>请依次点击：</span>
          <img id="kite-work-click-tip-img" :src="globalData.wordImgData.tipImage" class="kite-work-click-tip-img">
        </div>
        <div class="kite-captcha-content" @click="wordClick">
          <div class="kite-captcha-bg-img-box">
            <img id="kite-captcha-work-click-bg-img" :src="globalData.wordImgData.backgroundImage"
                 class="kite-captcha-work-click-bg-img">
            <canvas id="kite-captcha-slider-bg-canvas"></canvas>
            <div id="kite-bg-img-click-mask">
              <span
                  v-for="item in globalData.clickData"
                  :key="'click_' + item.count"
                  :style="{left: item.left, top: item.top }"
                  class="click-span"
              >{{ item.count }}</span>
            </div>
          </div>
          <div id="kite-captcha-tips" :class="globalData.tipsClass" class="kite-captcha-tips">{{
              globalData.tipsContent
            }}
          </div>
        </div>
      </div>

    </div>

    <div class="kite-captcha-slider-bottom">
      <img v-if="globalData.data.logoImage" :src="globalData.data.logoImage"
           alt="captcha-logo"
           class="kite-captcha-logo"/>
      <div v-show="showClose" class="kite-close-btn" title="关闭" @click="close"></div>
      <div class="kite-refresh-btn" title="刷新" @click="refresh(globalData.type)"></div>
      <div class="kite-info-btn" title="欢迎使用验证码组件"></div>
    </div>
  </div>

</template>

<script lang="ts" name="captcha" setup>
import {onMounted, reactive, ref} from 'vue'

import axios from 'axios';

const props = defineProps({
  /**
   * 是否显示
   */
  defaultShow: {type: Boolean, default: false},
  /**
   * 加载动画
   * ['slide', 'bounce', 'fade', 'lightSpeed', 'zoom']
   */
  loadingAnimation: {type: String, default: 'slide'},
  /**
   * 生成url
   */
  defaultGenUrl: {type: String, default: '/captcha/gen'},
  /**
   * 验证url
   */
  defaultCheckUrl: {type: String, default: '/captcha/check'},
  /**
   * 验证码类型
   * ['SLIDER', 'ROTATE', 'CONCAT', 'WORD_IMAGE_CLICK']
   */
  defaultType: {type: String, default: 'SLIDER'},
  /**
   * 背景图
   */
  backgroundImage: {type: String, default: ''},
  /**
   * 是否显示关闭按钮
   */
  showClose: {type: Boolean, default: true}
});

// 定义事件发射器
const emit = defineEmits<{
  (e: 'render-success', success: boolean): void
  (e: 'check-result', result: any): void
  (e: 'move-stop', data: any): void
}>()

// 全局数据状态
const globalData = reactive({
  data: {
    backgroundImage: '',
    backgroundImageWidth: 0,
    backgroundImageHeight: 0,
    sliderImageWidth: 0,
    sliderImageHeight: 0,
    templateImage: '',
    id: '',
    data: {randomY: 0},
    sliderButtonImage: '',
    boxBackgroundImage: props.backgroundImage,
    logoImage: '',
    loadingImage: ''
  },
  genUrl: props.defaultGenUrl,
  type: props.defaultType,
  checkUrl: props.defaultCheckUrl,
  checkData: {
    id: '',
    track: {}
  },
  startX: 0,
  startY: 0,
  end: 0,
  moveX: 0,
  movePercent: 0,
  // 滑动轨迹滑动时间等数据
  trackData: {
    bgImageWidth: 0,
    bgImageHeight: 0,
    sliderImageWidth: 0,
    sliderImageHeight: 0,
    startSlidingTime: new Date,
    endSlidingTime: new Date,
    trackList: [{}]
  },
  captchaClass: '',
  moveTracMaskStyles: {
    width: ''
  },
  // 移动按钮样式
  moveBtnStyles: {
    transform: 'translateX(0)',
    backgroundImage: ''

  },
  // 滑动样式
  sliderImgStyles: {
    transform: 'translateX(0)'
  },
  // 旋转样式
  rotateImgStyles: {
    transform: 'rotate(0deg)'
  },
  // 滚动还原样式
  concatImgStyles: {
    height: '',
    backgroundImage: '',
    backgroundPosition: '0px 0px'
  },
  // 滚动还原背景图片
  concatBgImgStyles: {
    backgroundImage: ''
  },
  // 文字点击数据
  wordImgData: {
    backgroundImage: '',
    tipImage: ''
  },
  // 文字点击数据
  clickData: [{count: null, left: 0, top: 0}],
  clickCount: 0,
  show: props.defaultShow,
  loading: false,
  tipsContent: '',
  tipsClass: '',
  activeTime: {
    startTime: 0,
    endTime: 0
  },
  renderSuccess: false
});

// 用于跟踪事件监听器状态
const isTracking = ref(false)

// 组件挂载时初始化
onMounted(() => {
  gen(props.defaultType)
})
/**
 * 初始化验证码容器
 */
const init = (): void => {
  let bgElement = document.getElementById('kite-captcha-bg-img-box');
  if (!bgElement) {
    bgElement = document.getElementById('kite-captcha-slider-concat-bg-img');
  }
  const sliderMoveBtnElement = document.getElementById('kite-captcha-slider-move-btn')
  if (bgElement && sliderMoveBtnElement) {
    globalData.trackData.bgImageWidth = bgElement.clientWidth
    globalData.trackData.bgImageHeight = bgElement.clientHeight
    globalData.end = globalData.trackData.bgImageWidth - sliderMoveBtnElement.clientWidth
    if (globalData.type === 'ROTATE') {
      globalData.trackData.sliderImageWidth = globalData.trackData.bgImageHeight
      globalData.trackData.sliderImageHeight = globalData.trackData.bgImageHeight
      globalData.trackData.bgImageWidth = globalData.end
    }
  }
};

/**
 * 生成滑动验证图片
 */
const gen = async (type?: string): Promise<void> => {
  globalData.show = true
  globalData.loading = true
  if (!type) {
    type = props.defaultType
  }
  try {
    const res = await axios.get(`${globalData.genUrl}?type=${type}`)
    setTimeout(() => render(res.data), 1000)
  } catch (error) {
    console.error('验证码获取失败:', error)
    globalData.loading = false
    tipsError('验证码加载失败')
  }
};
/**
 * 渲染验证码图片
 * @param res
 */
const render = (res: any): void => {
  reset()
  console.log('res', JSON.stringify(res))
  Object.assign(globalData.data, res.captcha);
  globalData.data.id = res.id;
  globalData.checkData.id = res.id;
  globalData.type = res.captcha.type;
  globalData.loading = false
  globalData.moveBtnStyles.backgroundImage = 'url(' + globalData.data.sliderButtonImage + ')'
  if (!globalData.data.boxBackgroundImage) {
    globalData.data.boxBackgroundImage = props.backgroundImage
  }
  console.log('globalData', JSON.stringify(globalData))
  let sliderElement = document.getElementById('kite-captcha-slider-move-img')
  if (!sliderElement) {
    sliderElement = document.getElementById('kite-captcha-slider-concat-img')
  }
  if (globalData.type !== 'WORD_IMAGE_CLICK') {
    if (sliderElement) {
      globalData.trackData.sliderImageWidth = sliderElement.clientWidth
      globalData.trackData.sliderImageHeight = sliderElement.clientHeight
    }
  }

  if (globalData.type === 'CONCAT') {
    globalData.concatBgImgStyles.backgroundImage = 'url(' + globalData.data.backgroundImage + ')'
    const backgroundImageHeight = globalData.data.backgroundImageHeight
    const height = ((backgroundImageHeight - globalData.data.data.randomY) / backgroundImageHeight) * 180
    globalData.concatImgStyles.backgroundImage = 'url(' + globalData.data.backgroundImage + ')'
    globalData.concatImgStyles.height = height + 'px'
    const bgImg = document.getElementById('kite-captcha')
    if (bgImg) {
      globalData.trackData.bgImageWidth = bgImg.clientWidth
      globalData.trackData.bgImageHeight = bgImg.clientHeight
    }

  } else if (globalData.type === 'WORD_IMAGE_CLICK') {
    globalData.wordImgData.backgroundImage = globalData.data.backgroundImage
    globalData.wordImgData.tipImage = globalData.data.templateImage
  }

  globalData.trackData.startSlidingTime = new Date()
  console.log('init', JSON.stringify(globalData.trackData))
  globalData.renderSuccess = true
  globalData.captchaClass = `animate__${props.loadingAnimation}InLeft`

  emit('render-success', globalData.renderSuccess)

};

/**
 * 验证
 */
const check = async (): Promise<void> => {
  if (globalData.type === 'WORD_IMAGE_CLICK') {
    const clickContent = document.getElementById('kite-bg-img-click-mask')
    const tipContent = document.getElementById('kite-work-click-tip-img')
    if (clickContent) {
      globalData.trackData.bgImageHeight = clickContent.clientHeight
      globalData.trackData.bgImageWidth = clickContent.clientWidth
    }
    if (tipContent) {
      globalData.trackData.sliderImageWidth = tipContent.clientWidth
      globalData.trackData.sliderImageHeight = tipContent.clientHeight
    }
  }
  const trackData = {
    ...globalData.trackData,
    startSlidingTime: formatDate(globalData.trackData.startSlidingTime),
    endSlidingTime: formatDate(globalData.trackData.endSlidingTime)
  }
  try {
    const res = await axios.post(`${globalData.checkUrl}?id=${globalData.checkData.id}`, trackData)
    const code = res.data.code
    emit('check-result', res.data)
    globalData.activeTime.endTime = new Date().getTime()

    if (code === 4000) {
      tipsError("验证码被黑洞吸走了")
      setTimeout(() => refresh(globalData.type), 1000)
      return
    }
    if (code === 200) {
      tipsSuccess()
      return
    }
    tipsError(null)
    setTimeout(() => refresh(globalData.type), 1000)
  } catch (error) {
    console.error('验证失败:', error)
    tipsError('验证失败，请重试')
    setTimeout(() => refresh(globalData.type), 1000)
  }

};
/**
 * 刷新
 */
const refresh = (type?: string): void => {
  globalData.captchaClass = `animate__${props.loadingAnimation}OutRight`
  gen(type)
}
/**
 * 重置
 */
const reset = (): void => {
  globalData.moveBtnStyles.transform = 'translateX(0)'
  globalData.moveTracMaskStyles.width = ''
  globalData.sliderImgStyles.transform = 'translateX(0)'
  globalData.rotateImgStyles.transform = 'rotate(0deg)'
  globalData.concatImgStyles.backgroundPosition = '0px 0px'
  globalData.concatImgStyles.backgroundImage = ''
  globalData.concatImgStyles.height = ''
  globalData.wordImgData.backgroundImage = ''
  globalData.wordImgData.tipImage = ''
  globalData.end = 0
  globalData.startX = 0
  globalData.startY = 0
  globalData.moveX = 0
  globalData.movePercent = 0
  globalData.checkData.track = {}
  globalData.checkData.id = ''
  globalData.data.id = ''
  globalData.trackData.startSlidingTime = new Date()
  globalData.trackData.endSlidingTime = new Date()

  globalData.trackData.trackList = []
  globalData.clickCount = 0
  globalData.clickData = []
  globalData.tipsContent = ''
  globalData.tipsClass = ''
  globalData.captchaClass = ''
  globalData.renderSuccess = false
  // 移除所有事件监听器
  if (isTracking.value) {
    window.removeEventListener('mousemove', move)
    window.removeEventListener('mouseup', up)
    window.removeEventListener('touchmove', move)
    window.removeEventListener('touchend', up)
    window.removeEventListener('mousemove', wordMove)
    isTracking.value = false
  }
};
/**
 * 关闭验证码容器
 */
const close = (): void => {
  globalData.show = false
};

/**
 * 日期格式化
 * @param date 日期对象
 */
const formatDate = (date: Date): string => {
  const dt = date
  const y = dt.getFullYear()
  const m = (dt.getMonth() + 1 + '').padStart(2, '0')
  const d = (dt.getDate() + '').padStart(2, '0')
  const hh = (dt.getHours() + '').padStart(2, '0')
  const mm = (dt.getMinutes() + '').padStart(2, '0')
  const ss = (dt.getSeconds() + '').padStart(2, '0')
  const sss = (dt.getMilliseconds() + '').padStart(2, '0')
  return `${y}-${m}-${d} ${hh}:${mm}:${ss}.${sss}`
};

const tipsError = (msg: string | null): void => {
  globalData.tipsContent = msg ? msg : '验证失败，请重新尝试';
  globalData.tipsClass = 'tips-error tips-on'
};

const tipsSuccess = (): void => {
  const sec = (globalData.activeTime.endTime - globalData.activeTime.startTime) / 1000
  globalData.tipsContent = `验证成功，耗时${sec}秒`
  globalData.tipsClass = 'tips-success tips-on'
};

/**
 * 切换验证方式
 * @param type
 */
const changeType = (type: string): void => {
  const TYPES: string[] = ['SLIDER', 'ROTATE', 'CONCAT', 'WORD_IMAGE_CLICK']
  if (type === 'RANDOM') {
    const randomNum = Math.floor(Math.random() * (TYPES.length - 1))
    type = TYPES[randomNum]
  }
  refresh(type)
}


/*
  * 滑动图片鼠标按下
  */
const sliderDown = (event: any): void => {
  init()
  globalData.activeTime.startTime = new Date().getTime();
  const targetTouches = event.originalEvent ? event.originalEvent.targetTouches : event.targetTouches
  let startX = event.pageX
  let startY = event.pageY
  if (startX === undefined) {
    startX = Math.round(targetTouches[0].pageX)
    startY = Math.round(targetTouches[0].pageY)
  }
  globalData.startX = startX
  globalData.startY = startY

  const pageX = globalData.startX
  const pageY = globalData.startY
  const startTime = globalData.trackData.startSlidingTime
  const track = {
    x: pageX - startX,
    y: pageY - startY,
    type: 'down',
    t: (new Date().getTime() - startTime.getTime())
  }
  globalData.trackData.trackList.push(track)
  console.log('start', startX, startY)
  // 添加事件监听器
  window.addEventListener('mousemove', move)
  window.addEventListener('mouseup', up)
  window.addEventListener('touchmove', move, {passive: false})
  window.addEventListener('touchend', up)
  isTracking.value = true
};

/**
 * 移动
 * @param event
 */
const move = (event: any): void => {
  if (event instanceof TouchEvent) {
    event = event.touches[0]
  }
  const pageX = Math.round(event.pageX)
  const pageY = Math.round(event.pageY)
  const startX = globalData.startX
  const startY = globalData.startY
  const startTime = globalData.trackData.startSlidingTime
  const end = globalData.end
  const bgImageWidth = globalData.trackData.bgImageWidth
  let moveX = pageX - startX
  const track = {
    x: pageX - startX,
    y: pageY - startY,
    type: 'move',
    t: (new Date().getTime() - startTime.getTime())
  }
  globalData.trackData.trackList.push(track)
  if (moveX < 0) {
    moveX = 0
  } else if (moveX > end) {
    moveX = end
  }
  globalData.moveX = moveX
  globalData.movePercent = moveX / bgImageWidth

  globalData.moveTracMaskStyles.width = moveX + 'px'
  globalData.moveBtnStyles.transform = 'translateX(' + moveX + 'px)'
  globalData.sliderImgStyles.transform = 'translateX(' + moveX + 'px)'
  globalData.rotateImgStyles.transform = 'rotate(' + (moveX / (end / 360)) + 'deg)'
  globalData.concatImgStyles.backgroundPosition = (moveX + 'px 0px')
  console.log('move', JSON.stringify(track))
}

/**
 * 鼠标松下,进行验证
 * @param event
 */
const up = (event: any): void => {
  // 移除事件监听器
  window.removeEventListener('mousemove', move)
  window.removeEventListener('mouseup', up)
  window.removeEventListener('touchmove', move)
  window.removeEventListener('touchend', up)
  isTracking.value = false
  if (event instanceof TouchEvent) {
    event = event.changedTouches[0]
  }
  globalData.trackData.endSlidingTime = new Date()
  const pageX = Math.round(event.pageX)
  const pageY = Math.round(event.pageY)
  const startX = globalData.startX
  const startY = globalData.startY
  const startTime = globalData.trackData.startSlidingTime
  const track = {
    x: pageX - startX,
    y: pageY - startY,
    type: 'up',
    t: (new Date().getTime() - startTime.getTime())
  }
  globalData.trackData.trackList.push(track)
  console.log('up', JSON.stringify(track))
  emit('move-stop', globalData.checkData)
  check()
};
/**
 * 点击文字
 */
const wordClick = (event: any): void => {

  globalData.clickCount++
  if (globalData.clickCount > 4) {
    return;
  }
  if (globalData.clickCount === 1) {
    globalData.activeTime.startTime = new Date().getTime()
    globalData.trackData.startSlidingTime = new Date()
    // move 轨迹
    window.addEventListener('mousemove', wordMove)
    isTracking.value = true
  }
  const track = {
    x: event.offsetX,
    y: event.offsetY,
    type: 'click',
    t: (new Date().getTime() - globalData.trackData.startSlidingTime.getTime())
  }
  globalData.trackData.trackList.push(track)
  const left = event.offsetX - 10
  const top = event.offsetY - 10
  globalData.clickData.push({
    count: globalData.clickCount,
    left: left + 'px',
    top: top + 'px'
  })

  if (globalData.clickCount === 4) {
    // 校验
    globalData.trackData.endSlidingTime = new Date()
    window.removeEventListener('mousemove', wordMove)
    isTracking.value = false
    globalData.checkData.track = {...globalData.trackData}
    emit('move-stop', globalData.checkData)
    check()
  }
}
/**
 * 文字点击移动
 * @param event
 */
const wordMove = (event: any): void => {
  if (event instanceof TouchEvent) {
    event = event.touches[0]
  }
  const track = {
    x: event.offsetX,
    y: event.offsetY,
    t: (new Date().getTime() - globalData.trackData.startSlidingTime.getTime()),
    type: 'move'
  }
  globalData.trackData.trackList.push(track)
}

defineExpose({
  changeType
})
</script>

<style scoped>
/** global css start **/
#kite-captcha-parent {
  box-shadow: 0 0 11px 0 #999;
  width: 318px;
  height: 318px;
  overflow: hidden;
  position: relative;
  z-index: 997;
  box-sizing: border-box;
  border-radius: 5px;
  padding: 8px;
}

/*背景图*/
#kite-captcha-parent #kite-captcha-bg-img {
  background-position: top;
  background-size: cover;
  background-repeat: no-repeat;
  z-index: -1;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  position: absolute;
  border-radius: 6px;
  background-color: #fff;
}

/*验证码主体*/
#kite-captcha-parent #kite-captcha-box {
  height: 260px;
  width: 100%;
  position: relative;
  overflow: hidden;
}

/*加载图*/
#kite-captcha-parent #kite-captcha-box #kite-captcha-loading {
  width: 50px;
  height: 50px;
  text-align: center;
  z-index: 998;
  position: absolute;
  top: 120px;
  left: 126px;
  -moz-user-select: none;
  -webkit-user-select: none;
  user-select: none;
}

#kite-captcha-parent #kite-captcha-box #kite-captcha {
  text-align: left;
  box-sizing: content-box;
  width: 300px;
  height: 260px;
}

#kite-captcha.kite-captcha-slider {
  z-index: 999;
  position: absolute;
  left: 0;
  top: 0;
  user-select: none;
}

#kite-captcha-parent #kite-captcha-slider-move-track-mask {
  border-width: 1px;
  border-style: solid;
  border-color: #00f4ab;
  width: 0;
  height: 32px;
  background-color: #a9ffe5;
  opacity: .5;
  position: absolute;
  top: -1px;
  left: -1px;
  border-radius: 5px;
}

/*验证码底部*/
#kite-captcha-parent .kite-captcha-slider-bottom {
  height: 19px;
  width: 100%;
}

/*logo图*/
#kite-captcha-parent .kite-captcha-slider-bottom .kite-captcha-logo {
  height: 30px;
  float: left;
}

#kite-captcha-parent .kite-captcha-slider-bottom .kite-close-btn, .kite-refresh-btn, .kite-info-btn {
  width: 20px;
  height: 20px;
  float: right;
  margin-right: 10px;
  margin-top: 10px;
  cursor: pointer;
  background-repeat: no-repeat;
  background-size: cover;
}

#kite-captcha-parent .kite-captcha-slider-bottom .kite-close-btn {
  background-image: url('');

}

#kite-captcha-parent .kite-captcha-slider-bottom .kite-refresh-btn {
  background-image: url('');
}

#kite-captcha-parent .kite-captcha-slider-bottom .kite-info-btn {
  background-image: url('');
}

#kite-captcha-parent #kite-captcha .kite-captcha-tips {
  height: 25px;
  width: 100%;
  position: absolute;
  bottom: -25px;
  left: 0;
  z-index: 999;
  font-size: 15px;
  line-height: 25px;
  color: #fff;
  text-align: center;
  transition: bottom .3s ease-in-out;
}

#kite-captcha-parent #kite-captcha .kite-captcha-tips.tips-error {
  background-color: #ff5d39
}

#kite-captcha-parent #kite-captcha .kite-captcha-tips.tips-success {
  background-color: #39c522
}

#kite-captcha-parent #kite-captcha .kite-captcha-tips.tips-on {
  bottom: 0 !important;
}

@keyframes myanimation {
  from {
    left: 0
  }
  to {
    left: 289px
  }
}

/** global css end **/

/** slider css start **/
#kite-captcha.kite-captcha-slider {
  z-index: 999;
  position: absolute;
  left: 0;
  top: 0;
  user-select: none;
}

#kite-captcha.kite-captcha-slider .kite-slider-tips {
  margin-bottom: 5px;
  font-weight: bold;
  font-size: 15px;
  line-height: normal;
  color: #000;
}

#kite-captcha.kite-captcha-slider .kite-captcha-content {
  width: 100%;
  height: 180px;
  position: relative;
  overflow: hidden;
}

#kite-captcha.kite-captcha-slider .kite-captcha-bg-img-box {
  width: 100%;
  height: 100%;
  position: absolute;
  transform: translate(0px, 0px);
}

#kite-captcha.kite-captcha-slider .kite-captcha-bg-img-box img {
  height: 100%;
  border-radius: 5px;
}

#kite-captcha #kite-captcha-slider-bg-canvas {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  border-radius: 5px;
}

#kite-captcha.kite-captcha-slider .kite-slider-move-img-box {
  height: 100%;
  position: absolute;
  transform: translate(0px, 0px);
}

#kite-captcha.kite-captcha-slider .kite-slider-move-img-box img#kite-captcha-slider-move-img {
  height: 100%;
}

#kite-captcha.kite-captcha-slider .kite-slider-move {
  height: 34px;
  width: 100%;
  margin: 11px 0;
  position: relative;
}

#kite-captcha.kite-captcha-slider .kite-slider-move .kite-slider-move-track {
  position: relative;
  height: 32px;
  line-height: 32px;
  text-align: center;
  background: #f5f5f5;
  color: #999;
  transition: 0s;
  font-size: 14px;
  box-sizing: content-box;
  border: 1px solid #f5f5f5;
  border-radius: 4px;
}

#kite-captcha.kite-captcha-slider .kite-slider-move .kite-slider-move-track .kite-slider-move-shadow {
  animation: myanimation 2s infinite;
  height: 100%;
  width: 5px;
  background-color: #fff;
  position: absolute;
  top: 0;
  left: 0;
  filter: opacity(0.5);
  box-shadow: 1px 1px 1px #fff;
  border-radius: 50%;
}

#kite-captcha.kite-captcha-slider .kite-slider-move .kite-slider-move-btn {
  transform: translate(0px, 0px);
  position: absolute;
  top: -6px;
  left: 0;
  width: 63px;
  height: 45px;
  background-color: #fff;
  background-repeat: no-repeat;
  background-size: contain;
  border-radius: 5px;
}

#kite-captcha.kite-captcha-slider .kite-slider-move .kite-slider-move-btn:hover {
  cursor: pointer;
}

/** slider css end **/

/** rotate css start **/
#kite-captcha.kite-captcha-rotate .kite-slider-move-img-box {
  height: 100%;
  text-align: center;
  position: initial;
}

#kite-captcha.kite-captcha-rotate .kite-slider-move-img-box img#kite-captcha-slider-move-img {
  height: 100%;
  transform: rotate(0deg);
  display: inline-block;
}

/** rotate css end **/

/** concat css start **/
#kite-captcha.kite-captcha-concat .kite-captcha-slider-concat-img {
  background-size: 100% 180px;
  position: absolute;
  transform: translate(0px, 0px);
  z-index: 1;
  width: 100%;
}

#kite-captcha.kite-captcha-concat .kite-captcha-slider-concat-bg-img {
  width: 100%;
  height: 100%;
  position: absolute;
  transform: translate(0px, 0px);
  background-size: 100% 180px;
}

/** concat css end **/

/** word_click css start **/
#kite-captcha.kite-captcha-work-click {
  position: relative;
  box-sizing: border-box;
}

#kite-captcha .kite-work-click-tips {
  position: relative;
  height: 40px;
  width: 100%;
}

#kite-captcha .kite-work-click-tips img {
  width: 130px;
  position: absolute;
  right: 15px;
}

#kite-captcha .kite-work-click-tips span {
  font-size: 20px;
  display: inline-block;
  height: 40px;
  line-height: 40px;
  position: absolute
}

#kite-captcha .kite-captcha-content #kite-bg-img-click-mask {
  width: 100%;
  height: 100%;
  position: absolute;
  left: 0;
  top: 0
}

#kite-captcha .kite-captcha-content #kite-bg-img-click-mask .click-span {
  position: absolute;
  left: 0;
  top: 0;
  border-radius: 50px;
  background-color: #409eff;
  width: 20px;
  height: 20px;
  text-align: center;
  line-height: 20px;
  color: #fff;
  border: 2px solid #fff
}

/** word_click css end **/


</style>
